dplyr
один из наиболее популярных пакетов для языка R, основным преимуществом которого является удобочитаемый и понятный синтаксис. Из недостатков данного пакета можно отметить, что при работе с данными большого объёма он значительно уступает в скорости вычислений например data.table
.
В этом видео уроке мы разберёмся с тем, как можно ускорить вычисления на dplyr
, за счёт бекендов dtplyr
и multidplyr
, а так же узнаем о том, как и зачем можно использовать бекенд dbplyr
, предназначенный для работы с базами данных.
Содержание
Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
Видео
Тайм коды:
-
00:00 Вступление
-
00:59 Какие бекенды мы рассмотрим
-
01:48 Цель
dtplyr
-
02:30 Синтаксис
dtplyr
-
03:33 Пример работы с
dtplyr
-
05:38 Как осуществляется перевод глаголов
dplyr
в синтаксисdata.table
-
07:51 Функция
show_query()
-
11:27 Почему dtplyr медленнее чем
data,table
-
13:24 Цель
dbplyr
-
13:40 Синтаксис
dbplyr
-
14:37 Пример работы с
dbplyr
-
16:54 Перевод
dplyr
глаголов в SQL запросы -
17:53 Как реализованы подзапросы в
dbplyr
-
18:25 Перевод неизвестных R функций и инфиксных операторов в SQL запрос
-
19:48 Проброс SQL команд в запросы
-
20:15 Как происходит перевод функций внутри
dplyr
глаголов в SQL запросы, функцияtranslate_sql()
-
21:26 Введение в
multidplyr
-
22:17 Варианты применения
multidplyr
-
22:50 Пример работы с
multidplyr
-
28:24 Какой пакет использовать
dtplyr
илиmultidplyr
-
29:27 Заключение
Презентация
Краткий конспект
dtplyr
Цель dtplyr
— позволить вам писать код dplyr, который под капотом транслируется в эквивалентный, но зачастую более быстрый, код data.table
.
Синтаксис dtplyr:
-
lazy_dt()
— создаёт объектdtplyr
для ленивых вычислений (первое действие); -
dplyr
— далее используем созданный с помощьюlazy_dt()
объект с глаголамиdplyr
, для реализации необходимых вычислений. При этом фактически никакие вычисления не производятся, а лишь оцениваются; -
show_query()
— показывает в какоеdata.table
выражение будут преобразованы ваши вычисления; -
as.data.table()
/as.data.frame()
/as_tibble()
— выполняет вычисления, и приводит результат к тому типу, который соответствует выбранной вами функции.
Примеры кода dtplyr
Пример вычисленний с dtplyr
# dtplyr library(data.table) library(dtplyr) library(dplyr, warn.conflicts = FALSE) # dtplyr использует ленивые вычисления mtcars2 <- lazy_dt(mtcars) # проверка результата mtcars2 %>% filter(wt < 5) %>% mutate(l100k = 235.21 / mpg) %>% # liters / 100 km group_by(cyl) %>% summarise(l100k = mean(l100k)) # но для выполнения вычислений следует использовать # as.data.table(), as.data.frame() или as_tibble() mtcars2 %>% filter(wt < 5) %>% mutate(l100k = 235.21 / mpg) %>% # liters / 100 km group_by(cyl) %>% summarise(l100k = mean(l100k)) %>% as_tibble() # более подробно о том, как осуществляется перевод кода df <- data.frame(a = 1:5, b = 1:5, c = 1:5, d = 1:5) dt <- lazy_dt(df) # посмотрим предварительную оценку перевода dt # если мы хотим посмотреть в какое выражение data.table # будет преобразован код dplyr используем show_query() dt %>% show_query() # простые глаголы ## filter() / arrange() - i dt %>% arrange(a, b, c) %>% show_query() dt %>% filter(b == c) %>% show_query() dt %>% filter(b == c, c == d) %>% show_query() ## select(), summarise(),transmute() - j dt %>% select(a:b) %>% show_query() dt %>% summarise(a = mean(a)) %>% show_query() dt %>% transmute(a2 = a * 2) %>% show_query() ## mutate - j + := dt %>% mutate(a2 = a * 2, b2 = b * 2) %>% show_query() # Другие глаголы dplyr ## rename - setnames dt %>% rename(x = a, y = b) %>% show_query() ## distinct - unique dt %>% distinct() %>% show_query() dt %>% distinct(a, b) %>% show_query() dt %>% distinct(a, b, .keep_all = TRUE) %>% show_query() # Операции объединения dt2 <- lazy_dt(data.frame(a = 1)) ## [.data.table dt %>% inner_join(dt2, by = "a") %>% show_query() dt %>% right_join(dt2, by = "a") %>% show_query() dt %>% left_join(dt2, by = "a") %>% show_query() dt %>% anti_join(dt2, by = "a") %>% show_query() ## merge() dt %>% full_join(dt2, by = "a") %>% show_query() # Группировка keyby dt %>% group_by(a) %>% summarise(b = mean(b)) %>% show_query() # Комбинации вызовов dt %>% filter(a == 1) %>% select(-a) %>% show_query() dt %>% group_by(a) %>% filter(b < mean(b)) %>% summarise(c = max(c)) %>% show_query()
dbplyr
Цель dbplyr
— позволить вам манипулировать данными, хранящимися в удалённых базах данных, так же как если бы они были дата фреймами в среде R. Данный бекенд переводит глаголы dplyr
в операторы SQL.
Синтаксис dbplyr
-
DBI
— для инициализации подключения к базе данных; -
tbl()
— для подключения к конкретной таблице; -
dplyr
— реализуем все вычисления на основе глаголовdplyr
, на данном этапе идёт только оценка вычислений; -
show_query()
— позволяет посмотреть в какой SQL запрос будет конвертирован написанный на предыдущем шаге код; -
collect()
— выполняет вычисления, и возвращает результат.
Пример кода dbplyr
Пример вычислений с dbplyr
library(dplyr) library(dbplyr) library(dplyr, warn.conflicts = FALSE) con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") copy_to(con, mtcars) # обращаемся к таблице mtcars2 <- tbl(con, "mtcars") # пока что это всего лишь оценка вычисления mtcars2 # dbplyr в действии ## генерация SQL запроса summary <- mtcars2 %>% group_by(cyl) %>% summarise(mpg = mean(mpg, na.rm = TRUE)) %>% arrange(desc(mpg)) ## просмотр запроса summary %>% show_query() ## выполнение запроса, и извлечение результата summary %>% collect() # автоматическая генерация подзапросов ------------------------------------ mf <- memdb_frame(x = 1, y = 2) mf %>% mutate( a = y * x, b = a ^ 2, ) %>% show_query() # неизвестные dplyr функции ----------------------------------------------- # любая неизветсная dplyr функция в запрос будет прокинута как есть mf %>% mutate(z = foofify(x, y)) %>% show_query() mf %>% mutate(z = FOOFIFY(x, y)) %>% show_query() # так же dbplyr умеет переводить инфиксные функции mf %>% filter(x %LIKE% "%foo%") %>% show_query() # использовать самописный SQL --------------------------------------------- ## для вставки SQL кода используйте sql() mf %>% transmute(factorial = sql("CAST(x AS FLOAT)")) %>% show_query() mf %>% filter(x == sql("ANY VALUES(1, 2, 3)")) %>% show_query()
multidplyr
Цель multidplyr
— позволить вам выполнять вычисления в многопоточном режиме, разделив таблицу на логические части, каждая часть обрабатывается на своём узле.
Варианты разбиения данных
multidplyr
позволяет вам разбить данные двумя способами:
-
Разбиение уже существующей в памяти таблицы с помощью
partition()
. -
Разбиение таблицы на части в момент загрузки.
Синтаксис multidplyr
-
new_cluster()
— создаёт кластер процессов; -
partition()
— разбивает существующий в памяти набор данных на части, что бы каждая отдельная часть обрабатывалась на своём узле кластера; -
cluster_assign_partition()
— разделяет вектор значений на части таким образом, что бы каждому узлу кластера досталась примерно одинаковое количество элементов; -
cluster_send()
— запускает выполнение вычислений на разны узлах кластера; -
party_df()
— создаёт секционированный дата фрейм; -
collect()
— возвращает секционированный дата фрейм к обычному режиму работы.
Примеры кода multidplyr
Пример вычислений с multidplyr
library(multidplyr) # создаЄм кластер cluster <- new_cluster(4) cluster # разбиение фрейма на кластера -------------------------------------------- library(nycflights13) flights1 <- flights %>% group_by(dest) %>% partition(cluster) flights1 # выполн¤ем вычислени¤ в многопоточном режиме flight_dest %>% summarise(delay = mean(dep_delay, na.rm = TRUE), n = n()) %>% collect() # чтение файлов разными кластерами ---------------------------------------- # создаЄм временную папку path <- tempfile() dir.create(path) # разбиваем файл по мес¤цам, # сохран¤¤ данные каждого мес¤ца в отдельный файл flights %>% group_by(month) %>% group_walk(~ vroom::vroom_write(.x, sprintf("%s/month-%02i.csv", path, .y$month))) # находим все файлы в директории, # и делим их так, чтобы каждому воркеру досталось (примерно) одинаковое количество файлов files <- dir(path, full.names = TRUE) cluster_assign_partition(cluster, files = files) # считываем файлы на каждом воркере # и используем party_df() дл¤ создани¤ секционированного фрейма данных cluster_send(cluster, flights2 <- vroom::vroom(files)) flights2 <- party_df(cluster, "flights2") flights2 # глаголы dplyr ----------------------------------------------------------- flights1 %>% summarise(dep_delay = mean(dep_delay, na.rm = TRUE)) %>% collect()
Какой бекенд выбрать
-
На данных среднего объёма предпочтительнее использовать
dtplyr
, накладные расходы на конвертацию кода минимальны, при высокой скорости вычислений. -
Если ваша таблица насчитывает более десятков миллионов строк, то есть смысл использовать
multidplyr
, накладные расходы на создание кластеров и обмен данных между ними выше, но и производительность выше. Но тут надо понимать, что в данных должна быть категориальная переменная, имеющая ограниченный набор уровней, и именно по этой переменной группируются ваши вычисления. Если такой переменной нет, то и особо прироста в скорости вычисленийmultidplyr
не даст, и даже возможен обратный эффект, когда накладные расходы превысят расходы на вычисления в однопоточном режиме. -
К
dbplyr
стоит прибегать в случае, когда данные с которыми вы работаете хранятся в базах данных. Данный бекенд направлен не столько на повышение производительности вычислений, сколько на то, что бы ваш R код не перемешивался с SQL запросами.
Заключение
В ходе этого урока, мы разобрались с тем, как ускорить вычисления реализованные с помощью dplyr
глаголов на данных большого объёма, а так же как работать с данными хранящимися в базах данных, не засоряя при этом код громоздкими SQL запросами.
Надеюсь материал предоставленный в этом видео уроке был вам полезен!
ссылка на оригинал статьи https://habr.com/ru/post/665680/